/*
 * control_unit.v
 *
 * Copyright 2024 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or visit <http://www.gnu.org/licenses/>.
 *
 *
 */


module control_unit(
    input               i_clk,
    input               i_rst,

    input      [31:0]   i_instr,
    input      [3:0]    i_NZVC,

    output reg          o_reg1_we,
    output reg          o_B_src,

    output reg [1:0]    o_reg1_src,
    output reg [1:0]    o_PC_src,
    output reg [3:0]    o_ALU_sel,

    output reg          o_lnk_trig,
    output reg          o_ram_we
);

    reg [3:0] NZVC;
    reg       alu_flags;

    always @(posedge i_clk) begin
        if (alu_flags)
            NZVC <= i_NZVC;
    end

    always @(*) begin
        if (i_rst) begin
            o_PC_src   = 2'b10;     /* simple +4 */
            o_reg1_we  = 1'b0;      /* DON'T write to reg1 */
            o_reg1_src = 2'b00;     /* from reg3 */
            o_B_src    = 1'b0;      /* don't care */
            o_ALU_sel  = 4'b1100;   /* don't care */
            o_ram_we   = 1'b0;      /* not memory operation */
            o_lnk_trig = 1'b0;      /* don't save link */

            alu_flags = 1'b0;
        end else begin

        case (i_instr[31:30])
            2'b00: begin    /* 0 format */
                if (i_instr[19:16] == 4'b0000) begin
                    /* MOV instruction */
                    o_PC_src   = 2'b10;     /* simple +4 */
                    o_reg1_we  = 1'b1;      /* write to reg1 */
                    o_reg1_src = 2'b11;     /* from reg3 */
                    o_B_src    = 1'b0;      /* don't care */
                    o_ALU_sel  = 4'b1100;   /* don't care */
                    o_ram_we   = 1'b0;      /* not memory operation */
                    o_lnk_trig = 1'b0;      /* don't save link */
                end else begin
                    /* math instructions */
                    o_PC_src   = 2'b10;     /* simple +4 */
                    o_reg1_we  = 1'b1;      /* write to reg1 */
                    o_reg1_src = 2'b01;     /* from ALU */
                    o_B_src    = 1'b0;      /* from reg3 */
                    o_ALU_sel  = i_instr[19:16]; /* according to op */
                    o_ram_we   = 1'b0;      /* not memory operation */
                    o_lnk_trig = 1'b0;      /* don't save link */
                end

                alu_flags = 1'b1;
            end

            2'b01: begin    /* 1 format */
                if (i_instr[19:16] == 4'b0000) begin
                    /* MOV instruction */
                    o_PC_src   = 2'b10;     /* simple +4 */
                    o_reg1_we  = 1'b1;      /* write to reg1 */
                    o_reg1_src = 2'b00;     /* from im field */
                    o_B_src    = 1'b0;      /* don't care */
                    o_ALU_sel  = 4'b1100;   /* don't care */
                    o_ram_we   = 1'b0;      /* not memory operation */
                    o_lnk_trig = 1'b0;      /* don't save link */
                end else begin
                    /* math instructions */
                    o_PC_src   = 2'b10;     /* simple +4 */
                    o_reg1_we  = 1'b1;      /* write to reg1 */
                    o_reg1_src = 2'b01;     /* from ALU */
                    o_B_src    = 1'b1 ;     /* from im field */
                    o_ALU_sel  = i_instr[19:16]; /* according to op */
                    o_ram_we   = 1'b0;      /* not memory operation */
                    o_lnk_trig = 1'b0;      /* don't save link */
                end

                alu_flags = 1'b1;
            end

            2'b10: begin    /* 2 format */
                case (i_instr[28])  /* V flag */
                    1'b0: begin                 /* word */
                        o_PC_src   = 2'b10;     /* simple +4 */
                        o_reg1_we  = ~i_instr[29]; /* based on U flag */
                        o_reg1_src = 2'b10;     /* from memory */
                        o_B_src    = 1'b0;      /* don't care */
                        o_ALU_sel  = 4'b1100;   /* don't care */
                        o_ram_we   = i_instr[29]; /* based on U flag */
                        o_lnk_trig = 1'b0;      /* don't save link */

                        alu_flags = 1'b0;
                    end

                    default: begin              /* byte (don't implemented) */
                        o_PC_src   = 2'bxx;
                        o_reg1_we  = 1'bx;
                        o_reg1_src = 2'bxx;
                        o_B_src    = 1'bx;
                        o_ALU_sel  = 4'bxxxx;
                        o_ram_we   = 1'bx;
                        o_lnk_trig = 1'bx;
                        alu_flags  = 1'bx;
                    end
                endcase
            end

            2'b11: begin    /* 3 format */
                o_reg1_we  = 1'b0;      /* DON'T write to reg1 */
                o_reg1_src = 2'b00;     /* don't care */
                o_B_src    = 1'b0;      /* don't care */
                o_ALU_sel  = 4'b1100;   /* don't care */
                o_ram_we   = 1'b0;      /* not memory operation */
                o_lnk_trig = i_instr[28]; /* based on V flag */

                alu_flags = 1'b0;

                case (i_instr[27:24])
                    4'b0000: o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);  /* jump always */

                    4'b0001: begin      /* if N */
                        if (NZVC[3]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b0010: begin      /* if Z */
                        if (NZVC[2]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b0011: begin      /* if C */
                        if (NZVC[0]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b0100: begin      /* if V */
                        if (NZVC[1]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b0101: begin      /* if ~N */
                        if (!NZVC[3]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b0110: begin      /* if ~Z */
                        if (!NZVC[2]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b0111: begin      /* if ~C */
                        if (!NZVC[0]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b1000: begin      /* if ~V */
                        if (!NZVC[1]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b1001: begin      /* if (N xor V) == 0 */
                        if (NZVC[1] ^ NZVC[3]) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    4'b1010: begin      /* if (N xor V) == 1 */
                        if (!(NZVC[1] ^ NZVC[3])) begin
                            o_PC_src = (i_instr[29] ? 2'b01 : 2'b00);
                        end else begin
                            o_PC_src   = 2'b10;     /* simple +4 */
                        end
                    end

                    default: o_PC_src = 2'bxx;
                endcase
            end

            default : begin
                o_PC_src   = 2'bxx;
                o_reg1_we  = 1'bx;
                o_reg1_src = 2'bxx;
                o_B_src    = 1'bx;
                o_ALU_sel  = 4'bxxxx;
                o_ram_we   = 1'bx;
                o_lnk_trig = 1'bx;
                alu_flags  = 1'bx;
            end
        endcase

        end
    end
endmodule
